//#include "stdafx.h"
#include "Utils.h"
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h> //it's said to add this header to use Sleep on windows platform,not tested
#endif
#ifdef __linux
#include <unistd.h>  //for sleep function
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>//for directory files list(in lyxutils::os::ls)
#endif
#include <fstream>
#include <ios>
#include <sstream>
#include <exception>
#include <cstring>
#include <ctime>  //for random seed generation(in function lyxutils::colors::ranad_color)

std::vector<std::string> lyxutils::str_utils::split(const std::string & str, const std::string & delimiter)
{
	std::vector<std::string> result = std::vector<std::string>();
	if (str == "") return result;
	char *chs = new char[str.length() + 1];
	std::strcpy(chs, str.c_str());
	char *d = new char[delimiter.length() + 1];
	std::strcpy(d, delimiter.c_str());
	char *p = std::strtok(chs, d);
	while (p) {
		std::string s = p;
		result.push_back(s);
		p = std::strtok(NULL, d);
	}
	delete chs, d;
	return result;
}

std::vector<std::string> lyxutils::str_utils::split_with_string(const std::string & str, const std::string & delString)
{
	std::vector<std::string> result = std::vector<std::string>();
	if (str == "") return result;
	size_t start = 0;
	size_t end = str.find(delString, start);
	while (end < str.length()) {
		if(end>start)result.push_back(str.substr(start, end - start));
		start = end + delString.length();
		end = str.find(delString, start);
	}
	if (start < str.length()) {
		result.push_back(str.substr(start, str.length() - start));
	}
	return result;
}

std::vector<std::string> lyxutils::str_utils::split_with_string_re(const std::string &str, const std::string &delString) {
    std::vector<std::string> result;
    if(str == "")return result;
    size_t start=0;
    size_t end = str.find(delString, start);
    while(end<str.length()){
        if(end>start)result.push_back(str.substr(start, end - start));
        else result.push_back("");
        start = end + delString.length();
        end = str.find(delString, start);
    }
    if(start < str.length()){
        result.push_back(str.substr(start, str.length() - start));
    }
    if(start == str.length() && str.substr(str.length()-delString.length(),delString.length())==delString){
        result.push_back("");
    }
    return result;
}

std::string lyxutils::str_utils::lower(const std::string & str)
{
	std::string res = str;
	std::transform(res.begin(), res.end(), res.begin(), ::tolower);
	return res;
}

std::string lyxutils::str_utils::upper(const std::string & str)
{
	std::string res = str;
	std::transform(res.begin(), res.end(), res.begin(), ::toupper);
	return res;
}

std::string lyxutils::str_utils::replace(const std::string &str, const std::string &oldStr, const std::string &newStr, int count)
{
	std::string res = str;
	size_t pos = res.find(oldStr, 0);
	while (count--!=0 && pos < res.length()) {
        res.replace(pos, oldStr.length(), newStr);
        pos = res.find(oldStr, pos + newStr.length());
    }
	return res;
}

std::string lyxutils::str_utils::replace(const std::string &str, const std::string &oldStr, const std::string &newStr, int start, int end) {
    if(start<0)start+=str.length();
    if(end<0)end+=str.length();
    if(start<0||start>str.length()||end<0||end>str.length())throw std::out_of_range("string index out of range");
    if(end<start)throw std::logic_error("end position at left side of start position is not allowed");
    std::string res=str.substr(start,end-start);
    size_t pos = res.find(oldStr, 0);
    while (pos < res.length()) {
        res.replace(pos, oldStr.length(), newStr);
        pos = res.find(oldStr, pos + newStr.length());
    }
    res=str.substr(0,start)+res+str.substr(end,str.length()-end);
    return res;
}

int lyxutils::str_utils::count(const std::string &str, const std::string &sub, int start, int end) {
    if(start<0)start+=str.length();
    if(end<0)end+=str.length();
    if(start<0||start>str.length()||end<0||end>str.length())throw std::out_of_range("string index out of range");
    if(end<start)throw std::logic_error("end position at left side of start position is not allowed");
    std::string temp=str.substr(start,end-start);
    size_t pos=temp.find(sub,0);
    int count=0;
    while(pos<temp.length()){
        count++;
        pos=temp.find(sub,pos+sub.length());
    }
    return count;
}

std::string lyxutils::str_utils::multiply(const std::string &str, int count) {
    if(count<0)throw std::invalid_argument("times of repetition should not be negative!");
    std::string result;
    while(count-->0){
        result+=str;
    }
    return result;
}

std::string lyxutils::str_utils::center(const std::string &str, int width, char c) {
    std::string result=str;
    if(width<=result.length())return result;
    int num=(width-str.length())/2;
    std::string pad=multiply(type2str(c),num);
    result=pad+result+pad;
    if(result.length()<width)result+=type2str(c);
    return result;
}

std::string lyxutils::str_utils::strip(const std::string &str, const std::string &chars) {
    auto it=str.begin();
    for(;it!=str.end();++it){
        if(lyxutils::str_utils::count(chars, type2str(*it), 0, chars.length())==0)break;
    }
    auto it2=str.end()-1;
    for(;it2!=it-1;--it2){
        if(lyxutils::str_utils::count(chars, type2str(*it2), 0, chars.length())==0)break;
    }
    std::string result=str.substr(it-str.begin(),it2+1-it);
    return result;
}

int lyxutils::str_utils::find(const std::string &str,const std::string &sub,int start) {
    if(sub.size()==0)throw std::logic_error("parameter illegal:try to find an empty string in a string");
    if(str.size()==0)return -1;
    int idx=str.find(sub[0],start);
    while(true){
        if(idx==-1 || idx+sub.size()>=str.size()){
            return -1;
        }
        int i=0;
        for(;i<sub.size();++i){
            if(str[idx+i]!=sub[i]){
                break;
            }
        }
        if(i==sub.size()){
            return idx;//completely matched
        }
        idx=str.find(sub[0],idx+1);
    }
}

std::string lyxutils::str_utils::frame(const std::string &title, const std::string &text, int width, int pad, char border, int shift) {
    if(pad<0)throw std::invalid_argument("pad should not be negative!");
    if(width < __MIN_WORDWRAP_WIDTH + 2 + 2 * pad)throw std::invalid_argument("width should be at least 1+pad+__MIN_WORDWRAP_WIDTH+pad+1!");
    if(title.length()>width)throw std::invalid_argument("width of title should be less than width!");
    std::vector<std::string> lines=word_wrap(text,width-2*pad-2);
    shift=shift<0?0:shift;
    std::string prefix=multiply(" ",shift);
    std::string top=prefix+center(title,width,border);
    std::string gap=prefix+border+center("",width-2,' ')+border;
    std::string bottom=prefix+center("",width,border);
    lines.insert(lines.begin(),gap);
    lines.insert(lines.begin(),top);
    lines.push_back(gap);
    lines.push_back(bottom);
    for(int i=2;i<lines.size()-2;++i){
        lines[i]=prefix+border+center(lines[i],width-2,' ')+border;
    }
    return join("\n",lines.begin(),lines.end());
}

std::string lyxutils::str_utils::left(const std::string &str, int width, char c) {
    std::string result=str;
    if(result.length()<width){
        result+=multiply(std::string(1,c),width-result.length());
    }
    return result;
}

std::string lyxutils::str_utils::right(const std::string &str, int width, char c) {
    std::string result=str;
    if(result.length()<width){
        result=multiply(std::string(1,c),width-result.length())+result;
    }
    return result;
}

std::vector<std::string> lyxutils::str_utils::word_wrap(const std::string &str, int width) {
    if(width < __MIN_WORDWRAP_WIDTH)throw std::invalid_argument("width should be greater than __MIN_WORDWRAP_WIDTH");
    std::vector<std::string> res;
    //get word wrap result of a single paragraph
    auto wrap_single_para=[&](const std::string &str, int width)->std::vector<std::string>{
        std::vector<std::string> result;
        if(str.length()<=width)result.push_back(lyxutils::str_utils::left(str,width,' '));
        else{
            auto isLetter=[](char c)->int{if((c>=65&&c<91)||(c>=97&&c<123))return 1;else return 0;};
            std::string line;
            for(int i=0;i<str.length();){
                if(line.length()==width-1&&isLetter(str[i])){
                    if(isLetter(*(line.end()-1))) {
                        line.push_back('-');
                    }
                    else{
                        line.push_back(' ');
                    }
                }
                else{
                    line.push_back(str[i++]);
                }
                if(line.length()==width||i==str.length()) {
                    result.push_back(lyxutils::str_utils::left(line,width,' '));
                    line.clear();
                }
            }
        }
        return result;
    };
    std::vector<std::string> paras=split(str,"\n");
    for(int i=0;i<paras.size();++i){
        std::vector<std::string> wrap_p=wrap_single_para(paras[i],width);
        for_each(wrap_p.begin(),wrap_p.end(),[&](const std::string &line){res.push_back(line);});
    }
    return res;
}

bool lyxutils::io::fileExists(std::string fileName)
{
	return access(fileName.c_str(), 0) == 0;
}

void lyxutils::colors::rgb2hsv(const std::vector<int>& rgb, std::vector<float>& hsv)
{
	int r = rgb[0];
	int g = rgb[1];
	int b = rgb[2];
	int cmax = r > g ? r : g;
	cmax = b > cmax ? b : cmax;
	int cmin = r < g ? r : g;
	cmin = b < cmin ? b : cmin;
	hsv.clear();
	for (auto i : { 0,1,2 })hsv.push_back(0);
	if (cmax != cmin)
	{
		if (cmax == r) {
			hsv[0] = 60 * (float)(g - b) / (cmax - cmin);
		}
		else if (cmax == g) {
			hsv[0] = 120 + 60 * (float)(b - r) / (cmax - cmin);
		}
		else {
			hsv[0] = 240 + 60 * (float)(r - g) / (cmax - cmin);
		}
		if (hsv[0] < 0)hsv[0] += 360;
	}
	if (cmax != 0)hsv[1] = float(cmax - cmin) / cmax;
	hsv[2] = cmax;
}

void lyxutils::colors::hsv2rgb(const std::vector<float>& hsv, std::vector<int>& rgb)
{
	float h = hsv[0];
	float s = hsv[1];
	float v = hsv[2];
	int hi = int(h / 60) % 6;
	float f = h / 60 - hi;
	float p = v*(1 - s);
	float q = v*(1 - f*s);
	float t = v*(1 - (1 - f)*s);
	rgb.clear();
	for (auto i : { 1,1,1 })rgb.push_back(0);
	switch (hi)
	{
	case 0:rgb[0] = (int)(v); rgb[1] = (int)(t); rgb[2] = (int)(p); break;
	case 1:rgb[0] = (int)(q); rgb[1] = (int)(v); rgb[2] = (int)(p); break;
	case 2:rgb[0] = (int)(p); rgb[1] = (int)(v); rgb[2] = (int)(t); break;
	case 3:rgb[0] = (int)(p); rgb[1] = (int)(q); rgb[2] = (int)(v); break;
	case 4:rgb[0] = (int)(t); rgb[1] = (int)(p); rgb[2] = (int)(v); break;
	case 5:rgb[0] = (int)(v); rgb[1] = (int)(p); rgb[2] = (int)(q); break;
	}
}

std::vector<int> lyxutils::colors::rand_color(std::vector<int> pit, std::vector<int> range) {
    for(int i=pit.size();i<3;++i)pit.push_back(0);
    for(int i=range.size();i<3;++i)range.push_back(64);
    srand((double)clock());
    std::vector<int> color;
    for(int i=0;i<3;++i){
        pit[i]%=256;//pit should be within 0~255
        range[i]%=128;//range should be within 0~127
        if(range[i]==0){
            color.push_back((pit[i]+128)%256);
            continue;
        }
        int lower=pit[i]-range[i]<0?0:pit[i]-range[i];
        int upper=pit[i]+range[i]>255?255:pit[i]+range[i];
        int c=rand()%(upper-lower)+lower;
        color.push_back((c+128)%256);
    }
    return color;
}

bool lyxutils::colors::ColorGenerator::exists(lyxutils::Vector3<int> color) {
    for(auto it=history.begin();it!=history.end();++it){
        if(it->equal(color,deny_range))return true;
    }
    return false;
}

void lyxutils::colors::ColorGenerator::rememberMe(lyxutils::Vector3<int> color) {
    //don't check the existence of color in history,assume color doesn't exist in history
    history.push_back(color);
    if(max_size>0 && history.size()>max_size){
        history.pop_front();
    }
}

lyxutils::colors::ColorGenerator::ColorGenerator(int deny_range,int max_history) {
    //default constructor
    this->deny_range=deny_range;
    this->max_size=max_history;
}

lyxutils::colors::ColorGenerator::ColorGenerator(lyxutils::Vector3<int> background,int deny_range,int max_history) {
    rememberMe(background);
    this->deny_range=deny_range;
    this->max_size=max_history;
}

lyxutils::Vector3<int> lyxutils::colors::ColorGenerator::operator()(bool never_appear_again) {
    srand((double)clock());
    geometry::Vector3<int> result={rand()%256,rand()%256,rand()%256};
    while(exists(result)){
        result={rand()%256,rand()%256,rand()%256};
    }
    if(never_appear_again)rememberMe(result);
    return result;
}

std::string lyxutils::debug::getTimeString()
{
	time_t timep;
	time(&timep);
	char tmp[64];
	strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&timep));
	return tmp;
}

void lyxutils::debug::sleep(int millisecs) {
#ifdef _WIN32
    Sleep(millisecs);
#endif
#ifdef __linux
    usleep(millisecs*1000);
#endif
}

size_t lyxutils::debug::estimate_file_rows(std::string file, int pre_read) {
    std::ifstream ifs(file);
    std::streampos pos=ifs.tellg();
    if(ifs.is_open()){
        //read pre_read empty lines
        for(int i=0;i<pre_read;++i){
            std::string line;
            if(!std::getline(ifs,line))return i;
        }
        pos=ifs.tellg();
        std::streampos firsts=pos/pre_read;
        ifs.seekg(0,std::ios::end);
        size_t total=ifs.tellg();
        ifs.close();
        return total/firsts;
    }
    else{
        throw std::runtime_error("lyxutils::debug::estimate_file_rows open file failed,file absolute name:"+os::abspath(file));
    }
}

void lyxutils::io::consoleProgressBar(int value, std::string delimiterL, std::string increment, std::string delimiterR, std::string status)
{
    std::cout << "\r";
	std::cout << delimiterL;
	for (int i = 0; i < value&&i<100; ++i) {
        std::cout << increment;
    }
	for (int i = value; i < 100; ++i) {
		std::cout << " ";
	}
	std::cout << delimiterR;
	std::cout << value << "% ";
	if (status == "" || value == 100) {
		std::cout << "completed";
	}
	else {
		std::cout << status;
	}
	if (value == 100) {
		std::cout << std::endl;
	}
	std::cout.flush();
}

void lyxutils::io::consoleProgressBar(int value, int total, int bar_width, char delimiterL, char increment, char space, char delimiterR) {
    if(value>total)return;
    if(total>bar_width && value!=total && value % (total / bar_width) != 0)return;//avoid redundant update
    std::cout<<"\r";
    std::cout<<delimiterL;
    int progress=value*bar_width/total;
    if(progress>bar_width)progress=bar_width;
    std::cout<<str_utils::left(str_utils::multiply(std::string({increment}),progress),bar_width,space);
    std::cout<<delimiterR;
    std::cout<<(value*100/total)<<"%";
    if(value==total)std::cout<<std::endl;
    std::cout.flush();
}

bool lyxutils::io::createFolder(const std::string folderPath)
{
#ifdef _WIN32
    if (_access(folderPath.c_str(), 0) == -1) {
        bool created=_mkdir(folderPath.c_str());
#endif
#ifdef __linux
    if (access(folderPath.c_str(), 0) == -1) {
        bool created=::mkdir(folderPath.c_str(),S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH);
#endif
        if (created == 0) {
            return true;
        }
        else {
            return false;
        }
    }
    return true;
}

std::ofstream * lyxutils::io::openLogFile(std::string fileName)
{
    std::ofstream *log = new std::ofstream();
    log->open(fileName, std::ios::app);
    return log;
}

void lyxutils::io::writeLog(std::ofstream & log, std::string message, std::string end)
{
    if (log.is_open()) {
        log << lyxutils::debug::getTimeString() << "\t" << message << end;
        log.flush();
    }
}

bool
lyxutils::io::__read_csv(const std::string &fileName, std::vector<std::vector<float> > &table,
        const std::string &sep, bool report, std::ostream &ssReport, int fields,
        bool read_header, std::vector<std::string> &headers, int numberOfRedirect,
        std::function<void(const std::vector<float> &row)> directFunc){
    if(report)ssReport << "<" << lyxutils::debug::getTimeString() << ">file report\nopening file:" << fileName;
    if (!fileExists(fileName)) {
        if(report)ssReport << "=>failed:file not exist!" << std::endl;
        return false;
    } else {
        if(report)ssReport << "=>succeeded." << std::endl;
    }
    std::fstream inFile(fileName);
    std::string line;
    int count = 0;
    int error_count = 0;
    //read header
    std::streampos pos = inFile.tellg();
    while (std::getline(inFile, line)) {
        if (line == "") {
            pos = inFile.tellg();
            continue;
        }
        count++;
        std::vector<std::string> field_values = lyxutils::str_utils::split(line, sep);
        fields = fields > field_values.size() ? (int) (field_values.size()) : fields;
        if (fields == -1) {
            fields = field_values.size();
        }
        if(numberOfRedirect==-1){//-1 means redirect all fields
            numberOfRedirect=field_values.size();
        }
        if(report)ssReport << "opened file has " << field_values.size() << " column(s),read first " << fields
                           << " column(s)" << std::endl;
        if (numberOfRedirect > fields) {
            numberOfRedirect = fields;
            if(report)ssReport
                        << "number of field to be redirected greater than number of fields. set number of redirect fields=number of fields="
                        << fields << std::endl;
        }
        table.clear();
        if (read_header) {
            headers = field_values;
            count--;//this line is header does not count as valid data
        } else {
            inFile.seekg(pos);//if this is actual data return to last line
            count--;
        }
        break;
    }
    //read actual data
    while (std::getline(inFile, line)) {
        if (line == "")continue;
        count++;
        std::vector<std::string> field_values = lyxutils::str_utils::split(line, sep);
        std::vector<float> data;
        try {
            //note:here we use stod to convert string to double then implicitly convert
            // double to float. if use stof to directly convert string to float, it will
            // raise std::out_of_range exception when converting small number, eg. "1e-40"
            std::for_each(field_values.begin(), field_values.end(),
                          [&](const std::string &str) { data.push_back(std::stod(str)); });
            if (data.size() < fields) {
                if(report)ssReport << "data line #" << count << " has less than required number of columns(" << fields
                                   << ") only "
                                   << field_values.size() << " columns exists,the rest will be filled with 0(s)"
                                   << std::endl;
            }
            while (data.size() < fields) {
                data.push_back(0);
            }
//                    std::vector<float> redirectFields = std::vector<float>();
            std::vector<float> redirectFields;//definition above is weird,idk why I did this
            int i = 0;
            for (; i < numberOfRedirect; ++i) {
                redirectFields.push_back(data[i]);
            }
            if (i < data.size()) {
                table.push_back(std::vector<float>());
            }
            for (; i < data.size(); ++i) {
                (table.end() - 1)->push_back(data[i]);
            }
            if (numberOfRedirect > 0) {
                directFunc(redirectFields);
            }
        }
        catch (std::exception) {
            error_count++;
            if(report)ssReport << "error occurred in line #" << count << ",this line is omitted;content of this line is:";
            for(auto it=field_values.begin();it!=field_values.end() && it!=field_values.begin()+20;++it){
                if(report)ssReport<<*it<<",";
            }
            if(report)ssReport << std::endl;
        }
    }
    if (inFile.is_open()) {
        inFile.close();
    }
    if(report)ssReport << "total non-empty line of read file:" << count << ",line with errors:" << error_count
                       << std::endl;
    return true;
}

bool lyxutils::io::read_csv(const std::string fileName, std::vector<std::vector<float> > &table, const std::string &sep) {
    std::vector<std::string> header;
    return __read_csv(fileName, table, sep, false, std::cout, -1, false, header, 0, [](const std::vector<float>){});
}

bool lyxutils::io::read_csv(const std::string fileName, std::vector<std::vector<float> > &table,
                                   std::vector<std::string> &header, const std::string &sep) {
    return __read_csv(fileName, table, sep, false, std::cout, -1, true, header, 0, [](const std::vector<float>){});
}

bool lyxutils::io::read_csv(std::string fileName, std::vector<std::vector<float> > &table, const std::string &sep,
                            std::ostream &out) {
    std::vector<std::string> header;
    return __read_csv(fileName, table, sep, true, out, -1, false, header, 0, [](const std::vector<float>){});
}

bool
lyxutils::io::read_csv(std::string fileName, std::vector<std::vector<float> > &table, std::vector<std::string> &header,
                       const std::string &sep, std::ostream &out) {
    return __read_csv(fileName, table, sep, true, out, -1, true, header, 0, [](const std::vector<float>){});
}

bool lyxutils::io::read_csv_customize(std::string fileName, std::function<void(const std::vector<float>&)> procRow,
                                      std::string sep) {
    std::vector<std::vector<float> > table;
    std::vector<std::string> header;
    return __read_csv(fileName,table,sep,false,std::cout,-1,false,header,-1,procRow);
}

bool lyxutils::io::read_csv_customize(std::string fileName, std::function<void(const std::vector<float>&)> procRow,
                                      std::string sep, std::ostream &out) {
    std::vector<std::vector<float> > table;
    std::vector<std::string> header;
    return __read_csv(fileName,table,sep,true,out,-1,false,header,-1,procRow);
}

lyxutils::io::CLParser::CLParser() {

}

void lyxutils::io::CLParser::parse(std::string command) {
    std::vector<std::string> argv;
    split_command(command,argv);
    parse(argv);
}

void lyxutils::io::CLParser::parse(int argc, char **argv) {
    std::vector<std::string> cmd;
    for(int i=0;i<argc;++i){
        cmd.push_back(argv[i]);
    }
    parse(cmd);
}

void lyxutils::io::CLParser::parse(std::string commands, std::string filename) {
    if (!lyxutils::io::fileExists(filename))throw std::runtime_error("file not exist!");
    std::fstream inFile(filename);
    std::string cl=commands;
    if(inFile.is_open()){
        std::string line;
        while(std::getline(inFile,line)){
            line=line.substr(0,str_utils::find(line,"//"));//remove comment started by `//`
            line=line.substr(0,str_utils::find(line,"#"));//remove comment started by `#`
            if(line.size()<2 || line[0]!='-'){
                continue;
            }
            line=str_utils::replace(line,":","=");//replace `:` to `=`
            cl+=" "+line;//make sure there is a space between two lines
        }
        inFile.close();
    }
    else{
        throw std::runtime_error("open exist " + filename + " failed");
    }
    cl=str_utils::replace(cl,"\n"," ");
    parse(cl);
}

void lyxutils::io::CLParser::setOptionPattern(int num_opts, const lyxutils::io::CLOption *options) {
    _opt_patterns.clear();
    _short2verbose.clear();
    for(int i=0;i<num_opts;++i){
        _opt_patterns[options[i].optName]=options[i];
        if(options[i].hasAbbr) {
            _short2verbose[str_utils::type2str(options[i].abbr)]=options[i].optName;
        }
    }
    _pattern_set=true;
}

void lyxutils::io::CLParser::clearOptionPattern() {
    _pattern_set=false;
    _opt_patterns.clear();
    _short2verbose.clear();
}

bool lyxutils::io::CLParser::hasOption(std::string optName) {
    return _opt_val.find(optName)!=_opt_val.end();
}

std::string lyxutils::io::CLParser::getOptionArg(std::string optName) {
    if(!hasOption(optName))return "";
    return _opt_val[optName];
}

std::vector<std::string> lyxutils::io::CLParser::getParameters() {
    return _parameters;
}

std::map<std::string, std::string> lyxutils::io::CLParser::getAllOptions() {
    return _opt_val;
}

void lyxutils::io::CLParser::parse(std::vector<std::string> argv) {
    _opt_val.clear();
    _parameters.clear();
    auto isShort=[](std::string chuck)->bool{return chuck.size() >= 2 && chuck[0] == '-' && chuck[1]!='-' && (chuck[1]<'0' || chuck[1]>'9');};
    auto isLong=[](std::string chuck)->bool{return chuck.size() > 2 && chuck.substr(0, 2) == "--";};
    auto isParameter=[](std::string chuck)->bool{return chuck[0]!= '-';};
    for(int i=1;i<argv.size();++i) {
        std::string chuck(argv[i]);
        std::vector<std::string> sv=str_utils::split(chuck,"=");
        std::string opt=sv[0];
        if (isShort(opt)) {//short option
            //remove duplicate
            if(opt.size()>2){
                std::map<char,int> sequal;
                for(int j=1;j<opt.size();++j){
                    sequal[opt[j]]=j;
                }
                std::vector<std::pair<char,int> > vpair;
                std::for_each(sequal.begin(),sequal.end(),[&](const std::pair<char,int> &p){vpair.push_back(p);});
                std::sort(vpair.begin(),vpair.end(),[](std::pair<char,int> p1,std::pair<char,int> p2)->bool{return p1.second<p2.second;});
                std::string opt_new="-";
                for(auto it=vpair.begin();it!=vpair.end();++it){
                    opt_new.push_back(it->first);
                }
                opt=opt_new;
            }
            if(_pattern_set){
                //replace every short option by lengthy ones
                for(int j=opt.size()-1;j>0;--j){
                    std::string sub=opt.substr(j,1);
                    std::string postfix=(sv.size()>1&&j==opt.size()-1)?"="+sv[1]:"";
                    if(_short2verbose.find(sub)!=_short2verbose.end()){
                        argv.insert(argv.begin()+i+1,"--"+_short2verbose[sub]+postfix);
                    }
                    else{
                        throw std::invalid_argument("unrecognized option:-"+sub);
                    }
                }
            }
            else{
                for(int j=1;j<opt.size();++j){
                    std::string sub=opt.substr(j,1);
                    if(j!=opt.size()-1){
                        _opt_val[sub]="";
                    }
                    else{
                        if(sv.size()>1){
                            _opt_val[sub]=sv[1];
                        }
                        else if(i+1<argv.size()&&isParameter(argv[i+1])){
                            _opt_val[sub]=std::string(argv[i+1]);
                            ++i;//next element has been used,need to skip it in next loop
                        }
                        else{
                            _opt_val[sub]="";//no argument if not found
                        }
                    }
                }
            }
        }
        else if (isLong(opt)) {//long option
            if(_pattern_set){
                if(_opt_patterns.find(opt.substr(2,opt.length()-2))!=_opt_patterns.end()){
                    if(_opt_patterns[opt.substr(2,opt.length()-2)].hasArg==2){
                        if(sv.size()>1){
                            _opt_val[opt.substr(2,opt.length()-2)]=sv[1];
                        }
                        else if(i+1<argv.size()&&isParameter(argv[i+1])){
                            _opt_val[opt.substr(2,opt.length()-2)]=std::string(argv[i+1]);
                            ++i;//next element has been used,need to skip it in next loop
                        }
                        else{
                            std::string full_name=opt.substr(2,opt.length()-2);
                            std::string type_hint="--"+full_name;
                            if(_opt_patterns[full_name].hasAbbr)type_hint+="("+str_utils::type2str(_opt_patterns[full_name].abbr)+")";
                            throw std::invalid_argument("option "+type_hint+" has no argument!");
                        }
                    }
                    else if(_opt_patterns[opt.substr(2,opt.length()-2)].hasArg==1){
                        if(sv.size()>1){
                            _opt_val[opt.substr(2,opt.length()-2)]=sv[1];
                        }
                        else{
                            _opt_val[opt.substr(2,opt.length()-2)]="";
                        }
                    }
                    else{
                        _opt_val[opt.substr(2,opt.length()-2)]="";//hasArg=0,no argument required
                    }
                }
                else{
                    throw std::invalid_argument("unrecognized option:"+opt);
                }
            }
            else{
                if(sv.size()>1){
                    _opt_val[opt.substr(2,opt.length()-2)]=sv[1];
                }
                else if(i+1<argv.size()&&isParameter(argv[i+1])){
                    _opt_val[opt.substr(2,opt.length()-2)]=std::string(argv[i+1]);
                    ++i;//next element has been used,need to skip it in next loop
                }
                else{
                    _opt_val[opt.substr(2,opt.length()-2)]="";
                }
                continue;
            }
        }
        else {//parameters
            _parameters.push_back(chuck);
        }
    }
}

std::string lyxutils::io::CLParser::generateHelp(std::string basicUsage, std::string description,
                                                 const std::vector<std::string> &_optNames,
                                                 const std::vector<std::string> &optDescription,
                                                 const std::vector<std::string> &optArgName) {
    const int leftCol=30;
    const int rightCol=50;
    if(_pattern_set && (_optNames.size() != optDescription.size()||_optNames.size()!=optArgName.size())){
        throw std::logic_error("optDescription size("+str_utils::type2str(optDescription.size())+
        ") or optArgName size("+str_utils::type2str(optArgName.size())+") != total number of options("+
        str_utils::type2str(_optNames.size())+")");
    }
    std::string result= "usage: " + basicUsage+"\n       ";
    std::vector<std::string> descrips=str_utils::word_wrap(description,80);
    for(std::string line:descrips)result+=line+"\n";
    if(_usageExamples.size()!=0){
        result+="examples:\n";
        for(auto exam:_usageExamples){
            result+="\t"+exam+"\n";
        }
    }
    result+="\noptions:\n";
    for(int i=0; i < _optNames.size(); ++i){
        if(_pattern_set&& _opt_patterns.find(_optNames[i]) == _opt_patterns.end())throw std::logic_error("optName("+str_utils::type2str(_optNames[i])+") not contained in option pattern!");
        std::string optField;
        std::string o="    ";
        if(_pattern_set&&_opt_patterns[_optNames[i]].hasAbbr){
            o= "-" + str_utils::type2str(_opt_patterns[_optNames[i]].abbr) + ", ";
        }
        std::string dash=_optNames[i].size()==1?"-":"--";
        optField+= "  " + o + dash + _optNames[i];
        std::string argName="value";
        if(optArgName.size()>i&&optArgName[i]!="")argName=optArgName[i];
        int arg_state=0;
        if(_pattern_set){
            arg_state=_opt_patterns[_optNames[i]].hasArg;
        }
        else{
            if(optArgName.size()<=i||optArgName[i]=="")arg_state=0;
            else arg_state=1;
        }
        if(arg_state==2){
            optField+="="+argName;
        }
        else if(arg_state==1){
            optField+="[="+argName+"]";
        }
        if(optField.length()<leftCol-2){
            optField=str_utils::left(optField,leftCol,' ');
        }
        else{
            optField+="\n"+str_utils::multiply(" ",leftCol);
        }
        result+=optField;
        std::vector<std::string> optDes=str_utils::word_wrap(optDescription[i],rightCol);
        for(int i=0;i<optDes.size();++i){
            if(i!=0)result+=str_utils::multiply(" ",leftCol);
            result+=optDes[i]+"\n";
        }
    }
    //record help message
    _last_usage=basicUsage;
    _last_description=description;
    _last_optNames=_optNames;
    _last_optDescription=optDescription;
    _last_argNames=optArgName;
    return result;
}

std::string lyxutils::io::CLParser::generateHelp() {
    return generateHelp(_last_usage,_last_description,_last_optNames,_last_optDescription,_last_argNames);
}

void lyxutils::io::CLParser::addUsageExample(const std::string &example) {
    _usageExamples.push_back(example);
}

void lyxutils::io::CLParser::split_command(std::string command, std::vector<std::string> &argv) {
    argv.clear();
    std::vector<std::string> splitted=str_utils::split(command," \t");
    for(auto it=splitted.begin();it!=splitted.end();){
        int pos=it->find('\"',0);
        if(pos==0 || (pos!= -1 && it->find('=',0)==pos-1)){
            std::vector<std::string>::iterator beg,end;
            beg=end=it;
            while(end!=splitted.end()){
                if(*(end->end()-1)=='\"' && (end->size()==1 || *(end->end()-2)!='\\')){//end->size()>0 is for sure
                    end++;
                    break;
                }
                end++;
            }
            std::string str=beg->substr(0,pos)+beg->substr(pos+1,beg->size()-pos-1);
            while(++beg!=end){
                str+=" "+*beg;
            }
            if(*(str.end()-1)=='\"'){
                str=str.substr(0,str.size()-1);
            }
            str=str_utils::replace(str,"\\\"","\"");
            argv.push_back(str);
            it=end;
        }
        else{
            argv.push_back(*it);
            ++it;
        }
    }
}

bool lyxutils::os::__isrelative(std::string file) {
    __check_path_validity(file);
#ifdef _WIN32
    file=str_utils::upper(file);
    if(file[0]>='A' && file[0]<='Z' && file.size()>1 && file[1]==':')return false;
    return true;
#endif
#ifdef __linux
    if(file[0]=='/')return false;
    return true;
#endif
}

bool lyxutils::os::exist(std::string fileName) {
    std::ifstream fin(fileName);
    if(!fin)return false;
    return true;
}

bool lyxutils::os::file(std::string fileName) {
#ifdef _WIN32
    struct _stat fileStat;
    if ((_stat(fileName.c_str(), &fileStat) == 0) && (fileStat.st_mode & _S_IFDREG))return true;
    return false;
#endif
#ifdef __linux
    struct stat fileStat;
    if ((stat(fileName.c_str(), &fileStat) == 0) && S_ISREG(fileStat.st_mode))return true;
    return false;
#endif
}

bool lyxutils::os::directory(std::string path) {
#ifdef _WIN32
    struct _stat fileStat;
    if ((_stat(path.c_str(), &fileStat) == 0) && (fileStat.st_mode & _S_IFDIR))return true;
    return false;
#endif
#ifdef __linux
    struct stat fileStat;
    if ((stat(path.c_str(), &fileStat) == 0) && S_ISDIR(fileStat.st_mode))return true;
    return false;
#endif
}

std::string lyxutils::os::pwd() {
    char buffer[__MAX_PATH_SIZE];
    getcwd(buffer,__MAX_PATH_SIZE);
    return buffer;
}

std::string lyxutils::os::parent(std::string path) {
    path=abspath(path);
    std::vector<std::string> all=str_utils::split(path,"/\\");
    auto pos=all.end();
#ifdef __linux
    if(pos!=all.begin())pos--;
    return "/"+str_utils::join("/",all.begin(),pos);
#endif
#ifdef _WIN32
    if(pos!=all.begin()+1)pos--;
    return str_utils::join("\\",all.begin(),pos);
#endif
}

std::vector<std::string> lyxutils::os::ls(std::string path, int flag) {
    std::vector<std::string> result;
    if(!directory(path))return result;
    while((*(path.end()-1)=='/'||*(path.end()-1)=='\\')&& path.size()>1)path=path.substr(0,path.size()-1);//remove trailing / or \
    //copy and modified code from https://blog.csdn.net/veghlreywg/article/details/91489648
#ifdef __linux
    DIR* open_dir = opendir(path.c_str());
    if (NULL == open_dir) {
        throw std::runtime_error("open folder "+path+" failed");
    }
    dirent* p = nullptr;
    while( (p = readdir(open_dir)) != nullptr) {
        std::string child=path+"/"+std::string(p->d_name);
        if(std::string(p->d_name)=="."||std::string(p->d_name)=="..")continue;//omit . and .. item in path
        if(flag==0||(flag==1 && file(child))||(flag==2 && directory(child))){
            result.push_back(child);
        }
    }
    closedir(open_dir);
    return result;
#endif
#ifdef _WIN32
    //opendir and readdir are functions in linux,in windows _findfirst,_findnext are possible substitudes
    //but it's not implemented here, I'll do it when I have to.
    //refer to:https://blog.csdn.net/stoneboy100200/article/details/8588319
#endif
}

std::vector<std::string> lyxutils::os::lsdir(std::string path) {
    return ls(path,2);
}

std::vector<std::string> lyxutils::os::lsfile(std::string path) {
    return ls(path,1);
}

std::string lyxutils::os::relpath(std::string target, std::string base) {
    target=abspath(target);
    base=abspath(base);
    std::vector<std::string> t=str_utils::split(target,"/\\");
    std::vector<std::string> b=str_utils::split(base,"/\\");
    auto tit=t.begin();
    auto bit=b.begin();
    std::string prefix="";
    std::string postfix="";
    for(;tit!=t.end()&&bit!=b.end();++tit,++bit){
        if(*tit!=*bit)break;
    }
    if(tit==t.end()&&bit==b.end())return ".";
    for(;bit!=b.end();++bit){
        prefix+="../";
    }
    for(;tit!=t.end();++tit){
        postfix+=*tit;
        if(tit!=t.end()-1)postfix+="/";
    }
    return prefix+postfix;
}

std::string lyxutils::os::abspath(std::string fileName) {
    if(!__isrelative(fileName)){
        //add patch for /path/to/target/. and /path/to/target/..
        //the absolute paths for above are /path/to/target and /path/to
        if(fileName.substr(fileName.size()-3,3)=="/.." ||
           fileName.substr(fileName.size()-3,3)=="\\.."){
            std::vector<std::string> parts=str_utils::split(fileName,"/\\");
#ifdef __linux
            if(parts.size()==1)return "/";
            return "/"+str_utils::join("/",parts.begin(),parts.end()-2);
#endif
#ifdef _WIN32
            //eg=>D:\.. D:\path\to\..
            if(parts.size()==2)return parts[0];//drive letter,can't go up from here
            return str_utils::join("\\",parts.begin(),parts.end()-2);
#endif
        }
        else if(fileName.substr(fileName.size()-2,2)=="/." ||
                fileName.substr(fileName.size()-2,2)=="\\."){
#ifdef __linux
            if(fileName.size()==2)return "/";
#endif
            //works the same in linux and windows
            return fileName.substr(0,fileName.size()-2);
        }
        //regular circumstance
        return fileName;
    }
    std::vector<std::string> parts=str_utils::split(fileName,"/\\");
    std::vector<std::string> base=str_utils::split(pwd(),"/\\");
    for(auto it=parts.begin();it!=parts.end();++it){
        if(*it=="."){
            continue;
        }
        else if(*it==".."){
#ifdef _WIN32
            if(base.size()>1)base.pop_back();//bottom is drive letter,can't go up from here
#endif
#ifdef __linux
            if(base.size()>0)base.pop_back();
#endif
        }
        else{
            base.push_back(*it);
        }
    }
#ifdef __linux
    return "/"+str_utils::join("/",base.begin(),base.end());
#endif
#ifdef _WIN32
    return str_utils::join("\\",base.begin(),base.end());
#endif
}

std::string lyxutils::os::fullname(std::string fileName) {
    return *(str_utils::split(fileName,"/\\").end()-1);
}

std::string lyxutils::os::basename(std::string fileName) {
    fileName=abspath(fileName);
    std::string fullname=*(str_utils::split(fileName,"/\\").end()-1);
    int pos=fullname.find_last_of('.',fullname.size());
    if(pos!=-1)return fullname.substr(0,pos);
    else return fullname;
}

std::string lyxutils::os::suffix(std::string fileName) {
    fileName=abspath(fileName);
    std::string fullname=*(str_utils::split(fileName,"/\\").end()-1);
    std::vector<std::string> divs=str_utils::split(fullname,".");
    if(divs.size()>1)return *(divs.end()-1);
    else return "";
}

bool lyxutils::os::cd(std::string target) {
    target=abspath(target);
    if(!directory(target))return false;
    if(target==pwd())return true;
    chdir(target.c_str());
    return true;
}

bool lyxutils::os::mkdir(std::string path, bool recursive) {
    path=abspath(path);
    if(directory(path))return true;
    std::string p=parent(path);
    auto mkp=[](std::string folder)->bool{
#ifdef _WIN32
        int created=_mkdir(folder.c_str());
#endif
#ifdef __linux
        int created=::mkdir(folder.c_str(),S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH);
#endif
        if(created==0)return true;
        else return false;
    };
    if(directory(p)){
        return mkp(path);
    }
    else if(recursive){
        return mkdir(p,recursive)&&mkp(path);
    }
    else{
        return false;
    }
}
